/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import {
	CancellationToken,
	commands,
	InlineCompletionContext,
	InlineCompletionEndOfLifeReason,
	InlineCompletionEndOfLifeReasonKind,
	InlineCompletionItem,
	InlineCompletionItemProvider,
	InlineCompletionList,
	InlineCompletionTriggerKind,
	PartialAcceptInfo,
	Position,
	Range,
	TextDocument,
	window,
} from 'vscode';
import { IInstantiationService, ServicesAccessor } from '../../../../../../util/vs/platform/instantiation/common/instantiation';
import { CopilotCompletion } from '../../../lib/src/ghostText/copilotCompletion';
import { handleGhostTextPostInsert, handleGhostTextShown, handlePartialGhostTextPostInsert } from '../../../lib/src/ghostText/last';
import { getInlineCompletions } from '../../../lib/src/inlineCompletion';
import { telemetry } from '../../../lib/src/telemetry';
import { wrapDoc } from '../textDocumentManager';

const postInsertCmdName = '_github.copilot.ghostTextPostInsert2';

export class GhostTextProvider implements InlineCompletionItemProvider {
	constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) { }

	async provideInlineCompletionItems(
		vscodeDoc: TextDocument,
		position: Position,
		context: InlineCompletionContext,
		token: CancellationToken
	): Promise<InlineCompletionList | undefined> {
		const textDocument = wrapDoc(vscodeDoc);
		if (!textDocument) {
			return;
		}

		// Opportunity ID is a unique ID generated by the client relating to a single "opportunity"
		// to provide some kind of suggestion to the user. Multiple requests might be made for a single
		// opportunity, for example requesting a completion as well as an edit suggestion. The single ID
		// allows us to correlate the different requests.
		const opportunityId = context.requestUuid;

		const formattingOptions = window.visibleTextEditors.find(e => e.document.uri === vscodeDoc.uri)?.options;

		const rawCompletions = await this.instantiationService.invokeFunction(getInlineCompletions, textDocument, position, token, {
			isCycling: context.triggerKind === InlineCompletionTriggerKind.Invoke,
			selectedCompletionInfo: context.selectedCompletionInfo,
			formattingOptions,
			opportunityId,
		});

		if (!rawCompletions) {
			return;
		}

		const items = rawCompletions.map(completion => {
			const { start, end } = completion.range;
			const newRange = new Range(start.line, start.character, end.line, end.character);
			return new InlineCompletionItem(completion.insertText, newRange, {
				title: 'Completion Accepted', // Unused
				command: postInsertCmdName,
				arguments: [completion],
			});
		});

		return { items };
	}

	handleDidShowCompletionItem(item: InlineCompletionItem) {
		const cmp = item.command!.arguments![0] as CopilotCompletion;
		this.instantiationService.invokeFunction(handleGhostTextShown, cmp);
	}

	handleDidPartiallyAcceptCompletionItem(item: InlineCompletionItem, info: number | PartialAcceptInfo) {
		if (typeof info === 'number') {
			return; // deprecated API
		}
		const cmp = item.command!.arguments![0] as CopilotCompletion;
		this.instantiationService.invokeFunction(handlePartialGhostTextPostInsert, cmp, info.acceptedLength, info.kind);
	}

	handleEndOfLifetime(completionItem: InlineCompletionItem, reason: InlineCompletionEndOfLifeReason) {
		// Send telemetry event when a completion is explicitly dismissed
		if (reason.kind !== InlineCompletionEndOfLifeReasonKind.Rejected) {
			return;
		}
		const cmp = completionItem.command?.arguments?.[0] as CopilotCompletion | undefined;
		if (!cmp) {
			return;
		}
		this.instantiationService.invokeFunction(telemetry, 'ghostText.dismissed', cmp.telemetry);
	}
}

/** Registers the commands necessary to use GhostTextProvider (but not GhostTextProvider itself) */
export function registerGhostTextDependencies(accessor: ServicesAccessor) {
	const instantiationService = accessor.get(IInstantiationService);
	const postCmdHandler = commands.registerCommand(postInsertCmdName, async (e: CopilotCompletion) => {
		instantiationService.invokeFunction(handleGhostTextPostInsert, e);
		try {
			await commands.executeCommand('github.copilot.survey.signalUsage', 'completions');
		} catch (e) {
			// Ignore errors from the survey command execution
		}
	});
	return postCmdHandler;
}
